package net.srcz.jsjvm.core;

import java.io.PrintStream;
import java.util.Set;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

// http://www.brics.dk/~mis/dOvs/jvmspec/ref-Java.html
public class JsMethodVisitor implements MethodVisitor {

	PrintStream ps;
	Set<String> classesRef;
	int access;
	String name;
	String desc;
	String signature;
	String[] exceptions;

	public JsMethodVisitor(int access, String name, String desc,
			String signature, String[] exceptions,Set<String> classesRef, PrintStream ps) {
		this.ps = ps;
		this.classesRef = classesRef;
		this.access = access;
		if((access & Opcodes.ACC_NATIVE) != 0)
			System.out.println(name + " " + desc + " : natif !");
		this.name = name;
		this.desc = desc;
		this.signature = signature;
		this.exceptions = exceptions;
		
		ps.printf("with(declareMethod(new JavaMethod('%s%s'))) {\n",name,desc);
	}

	@Override
	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		return new JsAnnotationVisitor(null,desc,classesRef,ps);
	}

	@Override
	public AnnotationVisitor visitAnnotationDefault() {
		return new JsAnnotationVisitor(null,null,classesRef,ps);
	}

	@Override
	public void visitAttribute(Attribute attr) {
		//ps.println("//this.attributes['%s'] = new Attribute(" + attr.type);
	}

	boolean codeStarted = false;
	@Override
	public void visitCode() {
		codeStarted = true;
		ps.printf("invoke = function() {\n");

		int nbArg = DescriptorUtil.getStackSize(desc);
		if((access & Opcodes.ACC_STATIC) == 0)
			nbArg++;
		for(int i=nbArg-1; i>=0; i--) {
			ps.printf("var localvar_%s = vm.po();\n",i);
		}
		ps.print("var nextLabel = 0;");
		ps.print("for(;;){");
		ps.print("switch(nextLabel){");
		ps.println("default:");
	}

	@Override
	public void visitEnd() {
		if(codeStarted) {
		ps.println("}");
		ps.println("}");
		ps.println("}");
		}
		ps.println("};");
	}

	@Override
	// GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
	public void visitFieldInsn(int opcode, String owner, String name,
			String desc) {
		addClassRef(owner);
		printOpcode(opcode, owner,name/*,desc*/);
	}

	@Override
	public void visitFrame(int type, int nLocal, Object[] local, int nStack,
			Object[] stack) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visitIincInsn(int var, int increment) {
		printOpcode(Opcodes.IINC, var, increment);
	}

	@Override
	// NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3,
	// ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2,
	// DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD,
	// CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE,
	// CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2,
	// SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL,
	// DMUL,
	// IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG,
	// ISHL,
	// LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L,
	// I2F,
	// I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP,
	// FCMPL,
	// FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN,
	// ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT
	public void visitInsn(int opcode) {
		printOpcode(opcode);
	}

	@Override
	// BIPUSH, SIPUSH, NEWARRAY
	public void visitIntInsn(int opcode, int operand) {
		printOpcode(opcode, operand);
	}

	@Override
	// IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT,
	// IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL
	// or IFNONNULL.
	public void visitJumpInsn(int opcode, Label label) {
		printOpcode(opcode, label.toString().substring(1));
	}

	@Override
	public void visitLabel(Label label) {
		ps.printf("case %s :\n",label.toString().substring(1));
	}

	@Override
	public void visitLdcInsn(Object cst) {
		printOpcode(Opcodes.LDC, ConstantConverter.convert(cst));
	}

	@Override
	public void visitLineNumber(int line, Label start) {
		// TODO
		ps.printf("// line %s = %s\n",line,start);
	}

	@Override
	public void visitLocalVariable(String name, String desc, String signature,
			Label start, Label end, int index) {
		//classesRef.add(name);
		// ps.println("localvar('"+name+"');");
		// replaceAll("local["+index+"]","local['"+name+"']", indexof start+":",
		// indeox end+":");
	}

	@Override
	public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
		ps.println("switch(vm.po()) {");
		for (int i = 0; i < keys.length; i++) {
			ps.println("case " + keys[i] + ":");
			ps.println("continue " + labels[i] + ";");
			ps.println("break;");
		}
		ps.println("default:");
		ps.println("continue " + dflt + ";");
		ps.println("break;");
		ps.println("}");
	}

	@Override
	public void visitMaxs(int maxStack, int maxLocals) {
		// TODO Auto-generated method stub

	}

	@Override
	// INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE or
	// INVOKEDYNAMIC.
	public void visitMethodInsn(int opcode, String owner, String name,
			String desc) {
		addClassRef(owner);
		int nbArg = DescriptorUtil.getStackSize(desc)+1;
		printOpcode(opcode, nbArg,owner,name,desc);
	}

	@Override
	// MULTIANEWARRAY
	public void visitMultiANewArrayInsn(String desc, int dims) {
		ps.println("var array = new Array();");
		ps.println("var nb = vm.po();");
		ps.println("for(i=0;i<nb;i++) {");
		createMultiArray("array[i]", 0, dims - 1);
		ps.println("}");
		ps.println("vm.pu(array);");
	}

	private void createMultiArray(String name, int pos, int dims) {
		char iter = (char) ('j' + pos);
		ps.println(name + " = new Array();");
		ps.println("var nb" + pos + " = vm.po();");
		ps.println("for(" + iter + "=0;" + iter + "<nb" + pos + ";" + iter
				+ "++) {");
		if (dims == 0) {
			ps.println(name + "[" + iter + "] = null;");
		} else {
			ps.println(name + "[" + iter + "] = new Array();");
			createMultiArray(name + "[" + iter + "]", pos + 1, dims - 1);
		}
		ps.println("}");
	}

	@Override
	public AnnotationVisitor visitParameterAnnotation(int parameter,
			String desc, boolean visible) {
		return new JsAnnotationVisitor(null, desc, classesRef, ps);
	}

	@Override
	public void visitTableSwitchInsn(int min, int max, Label dflt,
			Label[] labels) {
		ps.println("switch(vm.po()) {");
		for (int i = 0; i <= max - min; i++) {
			ps.println("case " + (min + i) + ":");
			ps.println("continue " + labels[i] + ";");
			ps.println("break;");
		}
		ps.println("default:");
		ps.println("continue " + dflt + ";");
		ps.println("break;");
		ps.println("}");
	}

	@Override
	public void visitTryCatchBlock(Label start, Label end, Label handler,
			String type) {
		ps.println("// visitTryCatchBlock " + start + " " + end + " " + handler
				+ " " + type);
		// ps.replaceFirst(start+":",start+": try {"
		// ps.replaceFirst(end+":","} catch(e) { "
		// + (type == null ? "" : "if(instanceof(e,"+type+") ") + continue
		// "+handler+ "} "+end+":");
	}

	@Override
	// NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
	public void visitTypeInsn(int opcode, String type) {
		addClassRef(type);
		printOpcode(opcode, type);
	}

	@Override
	// ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE
	// or RET.
	public void visitVarInsn(int opcode, int var) {
		printOpcode(opcode, var);
	}
	boolean debug = true;
	
	private void printOpcode(int opcode, Object...objects) {
		String code = CsvParser.opcodesJs.get(opcode);
		if(code == null) {
			System.err.println("unsupported opcode "+OpCodesNames.get(opcode));
			return;
		}
		try {
			ps.printf(code,objects);
			if(debug)
				ps.print(" //"+OpCodesNames.get(opcode));
			ps.println();
		} catch(Exception ex) {
			throw new RuntimeException("Error opcode "+OpCodesNames.get(opcode),ex);
		}
	}
	
	private void addClassRef(String ref) {
		if(ref.startsWith("["))
			ref = ref.substring(2);
		if(ref.endsWith(";"))
			ref = ref.substring(0,ref.length()-1);
		if(ref.length() != 0)
			classesRef.add(ref);
	}

}
